package org.zetaframework.job.aspect;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.zetaframework.annotation.log.JobLog;
import org.zetaframework.core.enums.JobLogStateEnum;
import org.zetaframework.core.model.JobLogDTO;
import org.zetaframework.core.utils.ZetaFunction;
import org.zetaframework.job.event.JobLogEvent;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 定时任务日志 切面
 *
 * @author gcc
 */
@Aspect
@Component
public class JobLogAspect {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();
    private static final Integer MAX_LENGTH = 65535;

    private final ApplicationContext context;
    public JobLogAspect(ApplicationContext context) {
        this.context = context;
    }

    @Pointcut("@annotation(org.zetaframework.annotation.log.JobLog)")
    public void JobLogAspect() {}

    /**
     * 执行方法之前
     *
     * @param joinPoint JoinPoint
     */
    @Before(value = "JobLogAspect()")
    public void doBefore(JoinPoint joinPoint) {
        // 记录操作开始时间
        START_TIME.set(System.currentTimeMillis());
    }

    /**
     * 执行方法之后
     *
     * @param joinPoint JoinPoint
     * @param result Object
     */
    @AfterReturning(pointcut = "JobLogAspect()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        publishEvent(joinPoint, result);
    }

    /**
     * 发生异常之后
     *
     * @param joinPoint JoinPoint
     * @param e Throwable
     */
    @AfterThrowing(pointcut = "JobLogAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        publishEvent(joinPoint, e);
    }

    /**
     * 发布日志存储事件
     *
     * @param joinPoint JoinPoint
     * @param result Object
     */
    private void publishEvent(JoinPoint joinPoint, Object result) {
        publishEvent(joinPoint, result, null);
    }

    /**
     * 发布日志存储事件
     *
     * @param joinPoint JoinPoint
     * @param exception Throwable
     */
    private void publishEvent(JoinPoint joinPoint, Throwable exception) {
        publishEvent(joinPoint, null, exception);
    }


    /**
     * 发布日志存储事件
     *
     * @param joinPoint JoinPoint
     * @param result Object
     * @param exception Throwable
     */
    private void publishEvent(JoinPoint joinPoint, Object result, Throwable exception) {
        // 方法耗时
        Long spendTime = getSpendTime();
        // 获取注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        JobLog jobLog = null;
        // 方法上的@JobLog注解
        if (method.isAnnotationPresent(JobLog.class)) {
            jobLog = method.getAnnotation(JobLog.class);
        }
        if (jobLog == null || !jobLog.enabled()) {
            return;
        }

        // 构造系统日志
        JobLogDTO logDTO = buildLogDTO(joinPoint, jobLog);
        logDTO.setCreateTime(LocalDateTime.now());
        logDTO.setStatus(JobLogStateEnum.SUCCESS.name());
        logDTO.setSpendTime(spendTime);
        logDTO.setException(getException(exception, () -> {
            logDTO.setStatus(JobLogStateEnum.FAIL.name());
        }));
        // 发布保存系统日志事件
        context.publishEvent(new JobLogEvent(logDTO));
    }


    /**
     * 获取方法耗时
     */
    private Long getSpendTime() {
        // 记录结束时间
        long spendTime = 0L;
        if (START_TIME.get() != null) {
            spendTime = System.currentTimeMillis() - START_TIME.get();
        }
        START_TIME.remove();
        logger.debug("定时任务执行耗时：{}", spendTime);
        return spendTime;
    }

    /**
     * 构造定时任务日志
     * @param joinPoint JoinPoint
     * @param jobLog JobLog
     * @return logDTO
     */
    private JobLogDTO buildLogDTO(JoinPoint joinPoint, JobLog jobLog) {
        JobLogDTO logDTO = new JobLogDTO();

        // 类路径
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        String name = joinPoint.getSignature().getName();
        String classPath = StrUtil.join(".", declaringTypeName, name);
        logDTO.setClassPath(classPath);

        // 操作描述
        logDTO.setDescription(getDescription(joinPoint, jobLog));
        // 任务处理器名称
        logDTO.setJobHandlerName(getJobHandlerName(joinPoint));

        try {
            logDTO.setJobParam(XxlJobHelper.getJobParam());
            logDTO.setShardIndex(XxlJobHelper.getShardIndex());
            logDTO.setShardTotal(XxlJobHelper.getShardTotal());

            XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
            if (xxlJobContext != null) {
                logDTO.setHandleCode(xxlJobContext.getHandleCode());
                logDTO.setHandleMsg(xxlJobContext.getHandleMsg());
            }
        } catch (Exception e) {
            logger.error("获取xxl-job任务参数失败", e);
        }

        return logDTO;
    }


    /**
     * 获取描述
     *
     * @param joinPoint
     * @param jobLog
     * @return String
     */
    private String getDescription(JoinPoint joinPoint, JobLog jobLog) {
        StringBuilder sb = new StringBuilder();

        if (StrUtil.isNotBlank(jobLog.value())) {
            sb.append(jobLog.value());
        } else {
            // 获取@XxlJob的value值
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();

            // 获取当前方法上的@XxlJob注解
            XxlJob xxlJob = method.getAnnotation(XxlJob.class);
            if (xxlJob != null && StrUtil.isNotBlank(xxlJob.value())) {
                sb.append(xxlJob.value());
            } else {
                // @JobLog没有value值、没有@XxlJob注解的情况下。显示方法名
                sb.append(method.getName());
            }
        }

        return sb.toString();
    }

    /**
     * 获取任务处理器名称
     *
     * @param joinPoint
     * @return String
     */
    private String getJobHandlerName(JoinPoint joinPoint) {
        // 获取@XxlJob的value值
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取当前方法上的@XxlJob注解
        XxlJob xxlJob = method.getAnnotation(XxlJob.class);
        if (xxlJob != null && StrUtil.isNotBlank(xxlJob.value())) {
            return xxlJob.value();
        }
        return "unknown";
    }

    /**
     * 获取异常
     * @param exception Throwable
     * @param block ZetaFunction
     * @return String
     */
    private String getException(Throwable exception, ZetaFunction block) {
        if (exception == null) { return StrUtil.EMPTY; }

        if(block != null) { block.run(); }
        return ExceptionUtil.stacktraceToString(exception, MAX_LENGTH);
    }
}
